﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using ZOIL.Framework.Core;
using System.ComponentModel;

namespace ZOIL.Framework.Presentation
{
    /// <summary>
    /// the zoomable information landscape
    /// </summary>
    public class ZInformationLandscape : ContentControl
    {
        #region Private fields

        /// <summary>
        /// an Index of all elements in the landscape
        /// </summary>
        private ArrayList _indexedElements = new ArrayList();

        private ZComponent _zoomComponent;

        #endregion

        #region Public Properties

        #region TransformationBorder

        /// <summary>
        /// tries to get the enclosing border that is used as a coordinate system
        /// </summary>
        public Border TransformationBorder
        {
            get
            {
                return Template.FindName("PART_Border", this) as Border;
            }
        }

        #endregion

        #region RenderTransform

        /// <summary>
        /// RenderTransform updates are delegated to the animation.
        /// </summary>
        public new Transform RenderTransform
        {
            get { return base.RenderTransform; }
            set { Animation.RenderTransform = value; }
        }

        #endregion

        #region FocusedControl

        /// <summary>
        /// when tracking is activated, the viewport of the landscape follows this control
        /// </summary>
        public FrameworkElement FocusedControl
        {
            get;
            private set;
        }

        #endregion

        #region FrameTransition

        /// <summary>
        /// The LandscapeAnimation that is applied in case of zooming
        /// </summary>
        public ZLandscapeAnimation Animation
        {
            get;
            private set;
        }

        #endregion

        #endregion

        #region Dependency Properties

        #region ConstraintsEnabled

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ConstraintsEnabledProperty = DependencyProperty.Register("ConstraintsEnabled", typeof(bool), typeof(ZInformationLandscape), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.None));

        /// <summary>
        /// 
        /// </summary>
        public bool ConstraintsEnabled
        {
            get { return (bool)this.GetValue(ConstraintsEnabledProperty); }
            set { this.SetValue(ConstraintsEnabledProperty, value); }
        }

        #endregion

        #region ConstrainingParent

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ConstrainingParentProperty = DependencyProperty.Register("ConstrainingParent", typeof(FrameworkElement), typeof(ZInformationLandscape), null);

        /// <summary>
        /// 
        /// </summary>
        public FrameworkElement ConstrainingParent
        {
            get { return (FrameworkElement)this.GetValue(ConstrainingParentProperty); }
            set { this.SetValue(ConstrainingParentProperty, value); }
        }

        #endregion

        #region EnableBoundaries

        public bool EnableBoundaries
        {
            get
            {
                return (bool)GetValue(EnableBoundariesProperty);
            }
            set
            {
                SetValue(EnableBoundariesProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableBoundariesProperty =
            DependencyProperty.RegisterAttached("EnableBoundaries", typeof(bool), typeof(ZInformationLandscape), new UIPropertyMetadata(true));

        #endregion

        #region ResetAngleToZeroOnClickZoom

        public bool ResetAngleToZeroOnClickZoom
        {
            get { return (bool)GetValue(ResetAngleToZeroOnClickZoomProperty); }
            set { SetValue(ResetAngleToZeroOnClickZoomProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ResetAngleToZeroOnClickZoom.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ResetAngleToZeroOnClickZoomProperty =
            DependencyProperty.Register("ResetAngleToZeroOnClickZoom", typeof(bool), typeof(ZInformationLandscape), new PropertyMetadata(true));

        #endregion

        #region CacheModeEnabled

        public bool CacheModeEnabled
        {
            get { return (bool)GetValue(CacheModeEnabledProperty); }
            set { SetValue(CacheModeEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CacheModeEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CacheModeEnabledProperty = DependencyProperty.Register("CacheModeEnabled", typeof(bool), typeof(ZInformationLandscape), new PropertyMetadata(false));

        #endregion

        #region CacheModeInterval

        public double CacheModeInterval
        {
            get { return (double)GetValue(CacheModeIntervalProperty); }
            set { SetValue(CacheModeIntervalProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CacheModeInterval.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CacheModeIntervalProperty = DependencyProperty.Register("CacheModeInterval", typeof(double), typeof(ZInformationLandscape), new PropertyMetadata(1000.0));

        #endregion

        #region IsConstantUpdateEnabled

        public bool IsConstantUpdateEnabled
        {
            get { return (bool)GetValue(IsConstantUpdateEnabledProperty); }
            set { SetValue(IsConstantUpdateEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsConstantUpdateEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsConstantUpdateEnabledProperty = DependencyProperty.Register("IsConstantUpdateEnabled", typeof(bool), typeof(ZInformationLandscape), new PropertyMetadata(false));

        #endregion

        #region ElementsNotInCacheMode

        public int ElementsNotInCacheMode
        {
            get { return (int)GetValue(ElementsInCacheModeProperty); }
            set { SetValue(ElementsInCacheModeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ElementsNotInCacheMode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ElementsInCacheModeProperty = DependencyProperty.Register("ElementsNotInCacheMode", typeof(int), typeof(ZInformationLandscape), new PropertyMetadata(0));

        #endregion

        #region MaxZoomFactor

        public double MaxZoomFactor
        {
            get { return (double)GetValue(MaxZoomFactorProperty); }
            set { SetValue(MaxZoomFactorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaxZoomFactor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaxZoomFactorProperty =
            DependencyProperty.Register("MaxZoomFactor", typeof(double), typeof(ZInformationLandscape), new PropertyMetadata(Double.MaxValue, OnMaxZoomFactorChanged));

        private static void OnMaxZoomFactorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (ZInformationLandscape)d;
            var oldMaxZoomFactor = (double)e.OldValue;
            var newMaxZoomFactor = target.MaxZoomFactor;
            target.OnMaxZoomFactorChanged(oldMaxZoomFactor, newMaxZoomFactor);
        }

        protected virtual void OnMaxZoomFactorChanged(double oldMaxZoomFactor, double newMaxZoomFactor)
        {
            if (Animation != null)
                Animation.MaxZoomFactor = newMaxZoomFactor;
        }

        #endregion

        #endregion

        #region attached Properties

        #region ZoomTarget

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ZoomTargetProperty = DependencyProperty.RegisterAttached("ZoomTarget", typeof(bool), typeof(ZInformationLandscape), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static bool GetZoomTarget(DependencyObject target)
        {
            return (bool)target.GetValue(ZoomTargetProperty);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <param name="value"></param>
        public static void SetZoomTarget(DependencyObject target, bool value)
        {
            target.SetValue(ZoomTargetProperty, value);
        }

        #endregion

        #region ZoomMargin

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ZoomMarginProperty = DependencyProperty.RegisterAttached("ZoomMargin", typeof(Thickness), typeof(ZInformationLandscape), new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.None));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static Thickness GetZoomMargin(DependencyObject target)
        {
            return (Thickness)target.GetValue(ZoomMarginProperty);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <param name="value"></param>
        public static void SetZoomMargin(DependencyObject target, Thickness value)
        {
            target.SetValue(ZoomMarginProperty, value);
        }

        #endregion

        #region ZoomModifier

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ZoomModifierProperty = DependencyProperty.RegisterAttached("ZoomModifier", typeof(ZoomModifier), typeof(ZInformationLandscape));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static ZoomModifier GetZoomModifier(DependencyObject target)
        {
            return (ZoomModifier)target.GetValue(ZoomModifierProperty);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <param name="value"></param>
        public static void SetZoomModifier(DependencyObject target, ZoomModifier value)
        {
            target.SetValue(ZoomModifierProperty, value);
        }

        #endregion

        #region ZoomModifierGroup

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ZoomModifierGroupProperty = DependencyProperty.RegisterAttached("ZoomModifierGroup", typeof(ZoomModifierCollection), typeof(ZInformationLandscape));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static ZoomModifierCollection GetZoomModifierGroup(DependencyObject target)
        {
            return (ZoomModifierCollection)target.GetValue(ZoomModifierGroupProperty);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <param name="value"></param>
        public static void SetZoomModifierGroup(DependencyObject target, ZoomModifierCollection value)
        {
            target.SetValue(ZoomModifierGroupProperty, value);
        }

        #endregion

        #endregion

        #region events

        #region AnimationChanged

        public event EventHandler<ZLandscapeAnimationEventArgs> AnimationChanged;

        public void RaiseAnimationChanged(ZLandscapeAnimationEventArgs e)
        {
            var handler = AnimationChanged;
            if (handler != null) handler(this, e);
        }

        #endregion

        #region ClickZoom

        /// <summary>
        /// 
        /// </summary>
        public static readonly RoutedEvent ClickZoomEvent = EventManager.RegisterRoutedEvent("ClickZoom", RoutingStrategy.Bubble, typeof(EventHandler), typeof(ZInformationLandscape));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="element"></param>
        /// <param name="handler"></param>
        public static void AddClickZoomHandler(DependencyObject element, RoutedEventHandler handler)
        {
            var uielement = element as UIElement;
            if (uielement != null)
            {
                uielement.AddHandler(ClickZoomEvent, handler);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="element"></param>
        /// <param name="handler"></param>
        public static void RemoveClickZoomHandler(DependencyObject element, RoutedEventHandler handler)
        {
            var uielement = element as UIElement;
            if (uielement != null)
            {
                uielement.RemoveHandler(ClickZoomEvent, handler);
            }
        }

        #endregion

        public event EventHandler AnimationInitialized;

        #endregion

        #region static members

        static ZInformationLandscape()
        {
            var assembly = Assembly.GetAssembly(typeof(ZInformationLandscape));
            var dict = new ResourceDictionary();
            var path = string.Format("pack://application:,,,/{0};component/Presentation/ZInformationLandscape.xaml", assembly.GetName().Name);
            dict.Source = new Uri(path, UriKind.RelativeOrAbsolute);
            Application.Current.Resources.MergedDictionaries.Add(dict);
        }

        #endregion

        #region ctor

        /// <summary>
        /// 
        /// </summary>
        public ZInformationLandscape()
        {
            Loaded += delegate
            {
                var isInDesignMode = DesignerProperties.GetIsInDesignMode(this);

                if (!isInDesignMode)
                    StartUpdate();
            };

            Unloaded += delegate
            {
                StopUpdate();
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="constrainingParent"></param>
        public ZInformationLandscape(FrameworkElement constrainingParent)
            : this()
        {
            ConstrainingParent = constrainingParent;
        }

        #endregion

        #region geometric & semantic zoom algorithm

        /// <summary>
        /// Each update call the IL does following:
        /// 1. If there's a current directed zooming target, the animation is updated.
        /// 2. If an index has been built, the indexed components are updated but NOT
        ///    the index itself.
        /// 3. If there's no index, the element tree is scanned recursively and each
        ///    element gets updated.
        /// </summary>
        private void Update()
        {
            //Console.WriteLine("Updating " + this.Name);

            // Focused controls are animation targets for directed zooming.
            // We therefore need to check if the animation needs to be updated.
            if (FocusedControl != null)
            {
                var e = FocusedControl;
                if (FocusedControl is ZComponent)
                    e = (FocusedControl as ZComponent).CurrentRepresentation;

                try
                {
                    var p = GetControlPosition(e);
                    if (Animation != null && p.IsValid && p != Animation.Position)
                        Animation.Goto(ApplyLandscapeConstraints(p), true);
                }
                // m_FocusedControl is disposed or temorarily not in the
                // visual tree, which we safely ignore.
                catch { }
            }

            if (!IsConstantUpdateEnabled && Animation.IsActive)
                return;

            // If we have elements in our index, we use the index instead of
            // recursing through the entire element tree each time.
            if (_indexedElements.Count > 0)
            {
                var count = _indexedElements.Count;
                for (var i = 0; i < count; i++)
                {
                    var element = _indexedElements[i] as DependencyObject;

                    if (element is ZComponent)
                        UpdateComponent(element as ZComponent);
                    else
                        UpdateElement(element);
                }


            }
            // Enter tree update recursion.
            else
                UpdateTree(this);
        }

        /// <summary>
        /// Updates the entire element tree.
        /// Recursive method.
        /// </summary>
        /// <param name="o">The tree's root.</param>
        private void UpdateTree(DependencyObject o)
        {
            if (o is ZComponent)
            {
                UpdateComponent(o as ZComponent);
            }
            UpdateElement(o);

            //if the updated zcomponent is not in the current viewport, then the children won't be either,
            //so lets skip the children
            if (o is ZComponent && ((o as ZComponent).ViewportIntersection == IntersectionDetail.Empty || (o as ZComponent).IsOffScreen))
                return;


            // Hidden subtrees are ignored.
            if (o is UIElement && (o as UIElement).Visibility != Visibility.Visible)
                return;

            // Recursion stops at nested information landscapes.
            if (o != this && o is ZInformationLandscape)
                return;

            // Recurse through children.
            int children = VisualTreeHelper.GetChildrenCount(o);
            for (int i = 0; i < children; i++)
                UpdateTree(VisualTreeHelper.GetChild(o, i));
        }

        private Window _window;

        /// <summary>
        /// Updates the representation of a ZComponent (i.e. semantic zoom)
        /// </summary>
        /// <param name="component"></param>
        private void UpdateComponent(ZComponent component)
        {
            if (TransformationBorder == null)
                return;

            if (_window == null)
                _window = Window.GetWindow(component);

            if (_window == null)
                return;

            // Register parent landscape
            component.SetValue(ZComponent.ParentInformationLandscapeProperty, this);

            // Calculate viewport intersection
            var windowSize = ZTreeHelper.GetClientRegion(this);

            var t = component.TransformToAncestor(_window);

            var upperLeft = t.Transform(new Point(0, 0));
            var upperRight = t.Transform(new Point(component.ActualWidth, 0));
            var lowerLeft = t.Transform(new Point(0, component.ActualHeight));
            var lowerRight = t.Transform(new Point(component.ActualWidth, component.ActualHeight));

            var allBoundingPoints = new List<Point> { upperLeft, upperRight, lowerLeft, lowerRight };

            var minBoundingX = allBoundingPoints.Min(point => point.X);
            var minBoundingY = allBoundingPoints.Min(point => point.Y);
            var maxBoundingX = allBoundingPoints.Max(point => point.X);
            var maxBoundingY = allBoundingPoints.Max(point => point.Y);

            var q = new Point(maxBoundingX, maxBoundingY);
            if (q.X < 0 || q.Y < 0)
                component.SetValue(ZComponent.ViewportIntersectionProperty, IntersectionDetail.Empty);
            else
            {
                //Point p = component.TranslatePoint(new Point(), getCurrentWindow());
                var p = new Point(minBoundingX, minBoundingY);
                if (p.X > windowSize.Width || p.Y > windowSize.Height)
                {
                    component.SetValue(ZComponent.ViewportIntersectionProperty, IntersectionDetail.Empty);
                }
                else
                {
                    component.SetValue(ZComponent.ViewportIntersectionProperty, IntersectionDetail.FullyInside);
                }
            }

            // Calculate rendered size
            var size = ZTreeHelper.GetRenderedSize(component);
            component.SetValue(ZComponent.RenderedSizeProperty, size);

            // Calculate percentage of the landscape size
            var renderedSize = ZTreeHelper.GetRenderedSize(TransformationBorder);
            size = new Size(component.RenderedSize.Width / renderedSize.Width, component.RenderedSize.Height / renderedSize.Height);
            component.SetValue(ZComponent.LandscapeSizePercentageProperty, size);

            // Calculate percentage of the window size
            size = new Size(component.RenderedSize.Width / windowSize.Width, component.RenderedSize.Height / windowSize.Height);
            component.SetValue(ZComponent.WindowSizePercentageProperty, size);

            // Update component
            component.UpdateRepresentation();
        }

        /// <summary>
        /// Updates a specific element. This includes off-screen check with the
        /// current viewport, and fixed-shape zooming updates.
        /// </summary>
        /// <param name="o">Element to be updated.</param>
        private void UpdateElement(DependencyObject o)
        {
            // Apply fixed-shape zooming constraints
            var modifier = (ZoomModifier)o.GetValue(ZoomModifierProperty);
            var collection = (ZoomModifierCollection)o.GetValue(ZoomModifierGroupProperty);

            if (modifier != null)
                modifier.Update(o, Animation.Position.Zoom);

            if (collection != null)
                foreach (var mod in collection)
                    mod.Update(o, Animation.Position.Zoom);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Checks if a landscape position is panning beyond the landscape's dimensions or zooms out too far.
        /// </summary>
        /// <param name="p">The landscape position to check.</param>
        /// <returns>The corrected landscape position.</returns>
        private ZLandscapePosition ApplyLandscapeConstraints(ZLandscapePosition p)
        {
            if (ConstraintsEnabled)
            {
                var windowSize = ZTreeHelper.GetClientRegion(this);
                var window = Window.GetWindow(this);
                var offset = TranslatePoint(new Point(), window);
                var constrainingWidth = Math.Min(ActualWidth, windowSize.Width);
                var constrainingHeight = Math.Min(ActualHeight, windowSize.Height);

                //add another constraining element, the parent
                if (ConstrainingParent != null)
                {
                    constrainingWidth = Math.Min(constrainingWidth, ConstrainingParent.ActualWidth);
                    constrainingHeight = Math.Min(constrainingHeight, ConstrainingParent.ActualHeight);
                }

                // Offset can be ignored if the IL fits into the window
                if (ActualWidth <= windowSize.Width)
                    offset.X = 0;
                if (ActualHeight <= windowSize.Height)
                    offset.Y = 0;

                // Zoomed out too far?
                if (ActualWidth * p.Zoom < constrainingWidth - offset.X)
                    p.Zoom = (constrainingWidth - offset.X) / ActualWidth;
                if (ActualHeight * p.Zoom < constrainingHeight - offset.Y)
                    p.Zoom = (constrainingHeight - offset.Y) / ActualHeight;

                // Calculate x and y margins
                var xOff = (ActualWidth * p.Zoom - constrainingWidth + offset.X) / p.Zoom;
                var yOff = (ActualHeight * p.Zoom - constrainingHeight + offset.Y) / p.Zoom;

                // Check up and left
                if (p.Pan.X > 0)
                    p.Pan.X = 0;
                if (p.Pan.Y > 0)
                    p.Pan.Y = 0;

                // Check down and right
                if (p.Pan.X < -xOff)
                    p.Pan.X = -xOff;
                if (p.Pan.Y < -yOff)
                    p.Pan.Y = -yOff;
            }

            return p;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// 
        /// </summary>
        public override void OnApplyTemplate()
        {
            var isInDesignMode = DesignerProperties.GetIsInDesignMode(this);

            if (!isInDesignMode)
            {
                base.OnApplyTemplate();
                if (Animation == null)
                {
                    if (TransformationBorder != null)
                        Animation = new ZLandscapeAnimation(this, TransformationBorder)
                                        {
                                            MaxZoomFactor = MaxZoomFactor
                                        };
                }
                else
                {
                    Animation.Target = TransformationBorder;
                    TransformationBorder.RenderTransform = Animation.RenderTransform;
                }

                if (Animation != null)
                {
                    Animation.Changed += OnAnimationChanged;
                    Animation.Finished += OnAnimationFinished;
                }

                if (AnimationInitialized != null)
                    AnimationInitialized(this, new EventArgs());

                // Register update event
                StartUpdate();

                // Update landscape constraints whenever the window size changes
                var window = Window.GetWindow(this);
                if (window != null)
                    window.SizeChanged += (sender, e) => Animation.Goto(ApplyLandscapeConstraints(Animation.Position), true);
            }
        }

        void OnAnimationChanged(object sender, ZLandscapeAnimationEventArgs e)
        {
            RaiseAnimationChanged(e);
        }

        void OnAnimationFinished(object sender, ZLandscapeAnimationEventArgs e)
        {

        }

        #region update control

        private bool _isUpdating;

        /// <summary>
        /// 
        /// </summary>
        public void StartUpdate()
        {
            if (!_isUpdating)
                CompositionTargetExtension.FrameUpdating += OnFrameUpdating;
            _isUpdating = true;
        }

        /// <summary>
        /// 
        /// </summary>
        public void StopUpdate()
        {
            if (_isUpdating)
                CompositionTargetExtension.FrameUpdating -= OnFrameUpdating;
            _isUpdating = false;
        }

        void OnFrameUpdating(object sender, EventArgs e)
        {
            Update();
        }

        #endregion

        /// <summary>
        /// Calculates the landscape position necessary to center a rectangle
        /// (in landscape coordinates) in the viewport.
        /// </summary>
        /// <param name="rect">Rectangle to center in landscape coordinates.</param>
        /// <returns></returns>
        public ZLandscapePosition GetCenteredViewPosition(Rect rect)
        {
            return GetCenteredViewPosition(rect, 0, 0);
        }

        /// <summary>
        /// Calculates the landscape position necessary to center a rectangle
        /// (in landscape coordinates) in the viewport.
        /// </summary>
        /// <param name="rect">Rectangle to center in landscape coordinates.</param>
        /// <param name="marginX">Horizontal margin on both sides.</param>
        /// <param name="marginY">Vertical margin on both sides.</param>
        /// <returns></returns>
        public ZLandscapePosition GetCenteredViewPosition(Rect rect, double marginX, double marginY)
        {
            // Update margin to componsate for the aspect ratio
            double newScale;

            var windowSize = ZTreeHelper.GetClientRegion(this);
            var width = Math.Min(windowSize.Width, ActualWidth);
            var height = Math.Min(windowSize.Height, ActualHeight);

            //add constraining parent feature(JS)
            if (ConstrainingParent != null)
            {
                width = Math.Min(width, ConstrainingParent.ActualWidth);
                height = Math.Min(height, ConstrainingParent.ActualHeight);
            }

            var newScaleX = width / rect.Width;
            var newScaleY = height / rect.Height;

            if (newScaleX < newScaleY)
            {
                newScale = newScaleX;
                //marginX += (height - (rect.Height * newScale)) / newScale;
                // found a bug? (JS)
                marginY += (height - (rect.Height * newScale)) / newScale;
            }
            else
            {
                newScale = newScaleY;
                marginX += (width - (rect.Width * newScale)) / newScale;
            }


            // Calculate translation and return new position
            return new ZLandscapePosition(newScale, new Point(
                -rect.Left + (marginX / 2.0),
                -rect.Top + (marginY / 2.0)));
        }

        /// <summary>
        /// Calculates the landscape position necessary to center the given element.
        /// </summary>
        /// <param name="element"></param>
        /// <returns>The position of the specified FrameworkElement in landscape coordinates.</returns>
        public ZLandscapePosition GetControlPosition(FrameworkElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            // Query margin
            var margin = (Thickness)element.GetValue(ZoomMarginProperty);
            var marginX = margin.Left + margin.Right;
            var marginY = margin.Top + margin.Bottom;

            // Calculate the element's bounding box            
            var p = element.TranslatePoint(new Point(), TransformationBorder);

            var t = GetRelativeTransformation(element);
            var boundingBox = new Rect(p.X, p.Y,
                element.ActualWidth + marginX,
                element.ActualHeight + marginY);

            //MZ: TODO: Ensure that t is never null by returning a standard matrix
            if (t != null)
            {
                var width = element.ActualWidth * t.Matrix.M11;
                var height = element.ActualHeight * t.Matrix.M22;

                if (width < 0)
                {
                    p.X += width;
                    width = Math.Abs(width);
                }

                if (height < 0)
                {
                    p.Y += height;
                    height = Math.Abs(height);
                }

                boundingBox = new Rect(p.X, p.Y, width + marginX, height + marginY);
            }

            return GetCenteredViewPosition(boundingBox, marginX, marginY);
        }

        /// <summary>
        /// calculates landscape position of a control. takes into account that the element may be rotated
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public ZLandscapePosition GetRotatedControlPosition(FrameworkElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            if (element == this)
                return GetControlPosition(element);

            var t = element.TransformToAncestor(TransformationBorder);

            var upperLeft = t.Transform(new Point(0, 0));
            var upperRight = t.Transform(new Point(element.ActualWidth, 0));
            var lowerLeft = t.Transform(new Point(0, element.ActualHeight));
            var lowerRight = t.Transform(new Point(element.ActualWidth, element.ActualHeight));

            var allBoundingPoints = new List<Point> { upperLeft, upperRight, lowerLeft, lowerRight };

            var minBoundingX = allBoundingPoints.Min(point => point.X);
            var minBoundingY = allBoundingPoints.Min(point => point.Y);
            var maxBoundingX = allBoundingPoints.Max(point => point.X);
            var maxBoundingY = allBoundingPoints.Max(point => point.Y);


            var boundingBox = new Rect(new Point(minBoundingX, minBoundingY), new Point(maxBoundingX, maxBoundingY));

            var margin = (Thickness)element.GetValue(ZoomMarginProperty);

            var marginX = margin.Left + margin.Right;
            var marginY = margin.Top + margin.Bottom;

            double newScale;

            var windowSize = ZTreeHelper.GetClientRegion(this);
            var width = Math.Min(windowSize.Width, ActualWidth);
            var height = Math.Min(windowSize.Height, ActualHeight);

            //add constraining parent feature(JS)
            if (ConstrainingParent != null)
            {
                width = Math.Min(width, ConstrainingParent.ActualWidth);
                height = Math.Min(height, ConstrainingParent.ActualHeight);
            }

            var newScaleX = width / boundingBox.Width;
            var newScaleY = height / boundingBox.Height;

            if (newScaleX < newScaleY)
            {
                newScale = newScaleX;
                //marginX += (height - (rect.Height * newScale)) / newScale;
                // found a bug? (JS)
                // marginY += (height - (rect.Height * newScale)) / newScale;
            }
            else
            {
                newScale = newScaleY;
                // marginX += (width - (rect.Width * newScale)) / newScale;
                // marginY += (height - (rect.Height * newScale)) / newScale;
            }
            marginX /= newScale;
            marginY /= newScale;

            boundingBox.Width += marginX;
            boundingBox.Height += marginY;

            return GetCenteredViewPosition(boundingBox, marginX, marginY);
        }

        /// <summary>
        /// returns the rotation angle of a component
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private static double GetControlAngle(FrameworkElement element)
        {
            var component = element as ZComponent;

            if (component != null)
            {
                return component.Angle;
            }

            var rotateTransform = element.RenderTransform as RotateTransform;

            if (rotateTransform != null)
            {
                return rotateTransform.Angle;
            }

            var transformGroup = element.RenderTransform as TransformGroup;

            if (transformGroup != null)
            {
                var angle = 0.0;
                foreach (var t in transformGroup.Children)
                {
                    var transform = t as RotateTransform;

                    if (transform != null)
                    {
                        angle += transform.Angle;
                    }
                }
                return angle;
            }

            return 0.0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="rect"></param>
        /// <returns></returns>
        public static Rect GetBoundingPoints(Rect rect)
        {
            var rectPoints = new List<Point>
                                 {
                                     rect.BottomLeft,
                                     rect.BottomRight,
                                     rect.TopRight,
                                     rect.TopLeft
                                 };

            var lr = new Point(0, 0);
            var ul = new Point(1000000000.0, 10000000000.0);

            foreach (var p in rectPoints)
            {
                if (p.X < ul.X)
                {
                    ul.X = p.X;
                }
                if (p.X > lr.X)
                {
                    lr.X = p.X;
                }
                if (p.Y < ul.Y)
                {
                    ul.Y = p.Y;
                }
                if (p.Y > lr.Y)
                {
                    lr.Y = p.Y;
                }
            }
            return new Rect(ul, lr);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public Rect GetControlPositionInLandscape(FrameworkElement target)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            // Query margin
            var margin = (Thickness)target.GetValue(ZoomMarginProperty);
            var marginX = margin.Left + margin.Right;
            var marginY = margin.Top + margin.Bottom;

            // Calculate the element's bounding box
            var p = target.TranslatePoint(new Point(), TransformationBorder);
            var t = GetRelativeTransformation(target);
            var boundingBox = new Rect(p.X, p.Y,
                target.ActualWidth + marginX,
                target.ActualHeight + marginY);

            //MZ: TODO: Ensure that t is never null by returning a standard matrix
            if (t != null)
            {
                boundingBox = new Rect(p.X, p.Y,
                        target.ActualWidth * t.Matrix.M11 + marginX,
                        target.ActualHeight * t.Matrix.M22 + marginY);
            }
            #region OLD Code, Margin adjustment not necessary for control allocation
            // Update margin to componsate for the aspect ratio
            //            double newScale;
            //            {
            //                Size windowSize = ZTreeHelper.GetClientRegion(this);
            //                double width = Math.Min(windowSize.Width, this.ActualWidth);
            //                double height = Math.Min(windowSize.Height, this.ActualHeight);

            //#if DEBUG
            //                Console.WriteLine("Old Size, width: " + width + ", height: " + height);
            //                Console.WriteLine("Old X: " + p.X + ", Y: " + p.Y);
            //#endif

            //                //add constraining parent feature(JS)
            //                if (this.ConstrainingParent != null)
            //                {
            //                    width = Math.Min(width, this.ConstrainingParent.ActualWidth);
            //                    height = Math.Min(height, this.ConstrainingParent.ActualHeight);

            //                    double screenProportion = windowSize.Width / windowSize.Height;

            //                    double correctedWidth = width;
            //                    double correctedHeight = height;

            //                    if (width / height > screenProportion)
            //                    {
            //                        correctedWidth = height * screenProportion;
            //                        //double diff = width - correctedWidth;
            //                        //if (diff > 0)
            //                        //{
            //                        //    double correctOffset = (diff / this.FrameTransition.Position.Zoom) / 2;
            //                        //    boundingBox.X += correctOffset;
            //                        //}
            //                        width = correctedWidth;

            //                    }
            //                    else if (height / width > screenProportion)
            //                    {
            //                        correctedHeight = width * screenProportion;
            //                        //double diff = height - correctedHeight;
            //                        //if (diff > 0)
            //                        //{
            //                        //    boundingBox.Y += (diff / this.FrameTransition.Position.Zoom) / 2;
            //                        //}
            //                        height = correctedHeight;

            //                    }
            //                }

            //#if DEBUG
            //                Console.WriteLine("New Size, width: " + width + ", height: " + height);
            //                Console.WriteLine("New X: " + boundingBox.X + ", Y: " + boundingBox.Y);
            //#endif

            //                // The Margin gets adjusted
            //                //double newScaleX = width / boundingBox.Width;
            //                //double newScaleY = height / boundingBox.Height;

            //                //if (newScaleX < newScaleY)
            //                //{
            //                //    newScale = newScaleX;
            //                //    marginY += (height - (boundingBox.Height * newScale)) / newScale;
            //                //}
            //                //else
            //                //{
            //                //    newScale = newScaleY;
            //                //    marginX += (width - (boundingBox.Width * newScale)) / newScale;
            //                //}
            //            }
            #endregion
            boundingBox = new Rect(boundingBox.X - (marginX / 2.0), boundingBox.Y - (marginY / 2.0), boundingBox.Width, boundingBox.Height);

            return boundingBox;
        }

        /// <summary>
        /// Method to calculate the current viewport of the landscape,which is seen, by the parent component
        /// </summary>
        /// <returns></returns>
        public Rect CurrentViewportPosition
        {
            get
            {
                //calculate, which viewport of the landscape is seen!
                if (ConstrainingParent != null)
                {
                    var offset = ConstrainingParent.TranslatePoint(new Point(), Window.GetWindow(ConstrainingParent));
                    //offset is always 0,0? why? check this again (JS)
                    var p = TransformWindowToLandscapeCoordinates(offset);

                    var width = ConstrainingParent.ActualWidth / Animation.Position.Zoom;
                    var height = ConstrainingParent.ActualHeight / Animation.Position.Zoom;
                    return new Rect(p, new Size(width, height));
                }
                return new Rect();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public TransformGroup LandscapeTransform
        {
            get
            {
                var currentScale = Animation.Position.Zoom;
                var myTranslateTransform = new TranslateTransform(Animation.Position.Pan.X * currentScale, Animation.Position.Pan.Y * currentScale);
                var myScaleTransform = new ScaleTransform(currentScale, currentScale);
                var myTransformGroup = new TransformGroup();

                myTransformGroup.Children.Add(myScaleTransform);
                myTransformGroup.Children.Add(myTranslateTransform);

                return myTransformGroup;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public TransformGroup InverseLandscapeTransform
        {
            get
            {
                var currentScale = Animation.Position.Zoom;
                var myTranslateTransform = new TranslateTransform(-(Animation.Position.Pan.X / currentScale), -(Animation.Position.Pan.Y / currentScale));
                var myScaleTransform = new ScaleTransform(1.0 / currentScale, 1.0 / currentScale);
                var myTransformGroup = new TransformGroup();

                myTransformGroup.Children.Add(myScaleTransform);
                myTransformGroup.Children.Add(myTranslateTransform);

                return myTransformGroup;
            }
        }

        #endregion

        #region Transformation Helper

        /// <summary>
        /// Calculates a matrix to transform the information landscape coordinates into the element's coordinate system.
        /// </summary>
        /// <param name="element">Target element.</param>
        /// <returns>Transformation matrix.</returns>
        public MatrixTransform GetRelativeTransformation(Visual element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            //This prevents throwing of InvalidOperationException, due to the TransformationBorder not
            //being an ancestor of the Landscape, but a child
            if (this == element)
            {
                //MZ: TODO: determine if we can return a standard MatrixTransform instead of null
                return null;
            }

            try
            {
                return element.TransformToAncestor(TransformationBorder) as MatrixTransform;

            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Transforms a point in Window Coordinates to a point in landscape coordinates
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Point TransformWindowToLandscapeCoordinates(Point point)
        {
            return ZTreeHelper.GetGlobalTransformation(this.TransformationBorder).Inverse.Transform(point);
        }

        /// <summary>
        /// Transforms a rect in Window Coordinates to a rect in landscape coordinates
        /// </summary>
        /// <param name="rect"></param>
        /// <returns></returns>
        public Rect TransformWindowToLandscapeCoordinates(Rect rect)
        {
            return ZTreeHelper.GetGlobalTransformation(this.TransformationBorder).Inverse.TransformBounds(rect);
        }

        /// <summary>
        /// Transforms a point in landscape Coordinates to a point in window coordinates
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Point TransformLandscapeToWindowCoordinates(Point point)
        {
            return ZTreeHelper.GetGlobalTransformation(TransformationBorder).Transform(point);
        }

        /// <summary>
        /// Transforms a rect in landscape Coordinates to a rect in window coordinates
        /// </summary>
        /// <param name="rect"></param>
        /// <returns></returns>
        public Rect TransformLandscapeToWindowCoordinates(Rect rect)
        {
            return ZTreeHelper.GetGlobalTransformation(TransformationBorder).TransformBounds(rect);
        }

        #endregion

        #region Panning / Zooming

        /// <summary>
        /// Moves the viewport to a specific landscape position.
        /// </summary>
        /// <param name="position">The landscape positio to zoom to.</param>
        /// <param name="animated">Animated zoom?</param>
        public void Goto(ZLandscapePosition position, bool animated)
        {
            if (Animation == null)
                return;

            Animation.Goto(position, animated);
            FocusedControl = null;
        }

        /// <summary>
        /// Zooms the information landscape by a given factor, using a landscape
        /// position as reference point.
        /// </summary>
        /// <param name="target">The zooming reference point.</param>
        /// <param name="by">The factor by which scale is increased/decreased.</param>
        /// <param name="animated">Animated zoom?</param>
        public virtual void ZoomBy(Point target, double by, bool animated)
        {
            if (Animation == null)
                return;

            var p = Animation.Position;

            // Apply zoom
            p.Zoom *= by;

            // Get landscape dimensions
            var width = ActualWidth;
            var height = ActualHeight;
            var deltaWidth = width - width * by;
            var deltaHeight = height - height * by;

            p.Pan.X += target.X / width * deltaWidth / p.Zoom;
            p.Pan.Y += target.Y / height * deltaHeight / p.Zoom;
            Animation.Goto(ApplyLandscapeConstraints(p), animated);
            FocusedControl = null;
        }

        /// <summary>
        /// Zooms onto a specifc target object.
        /// If the target isn't a zoom target, the closest ancestor that is
        /// a zoom target is taken instead (if it exists).
        /// </summary>
        /// <param name="element">The element to zoom on.</param>
        /// <param name="track">If true, the element is kept centered even
        /// after it has been moved/resized.</param>
        /// <param name="animated">Animated zoom?</param>
        public void ZoomOnto(FrameworkElement element, bool track, bool animated)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            if (!(bool)element.GetValue(ZoomTargetProperty))
            {
                element = ZTreeHelper.GetClosestZoomTarget(this, element);
                if (element == null)
                    return;
            }

#if DEBUG
            ZDebug.Log(DebugLevel.Info, DebugCategories.InformationLandscape, String.Format(
                "Zooming onto {0}",
                (!String.IsNullOrEmpty(element.Name) ? element.Name : element.ToString())));
#endif

            if (track)
                FocusedControl = element;

            else if (Animation != null)
            {
                if (_zoomComponent != null)
                    _zoomComponent.ZoomStarted();

                _zoomComponent = element as ZComponent;

                if (ResetAngleToZeroOnClickZoom)
                {
                    // Angle gets zero on clickzoom
                    if (_zoomComponent != null)
                        _zoomComponent.Angle = 0.0;
                }

                FocusedControl = null;

                Animation.Finished += AnimationFinished;

                Animation.Goto(ApplyLandscapeConstraints(GetRotatedControlPosition(element)), animated);
            }
        }

        void AnimationFinished(object sender, ZLandscapeAnimationEventArgs e)
        {
            if (_zoomComponent != null)
                _zoomComponent.ZoomFinished();

            Animation.Finished -= AnimationFinished;
        }

        /// <summary>
        /// Tries to find the next istance in the element's zoom target
        /// ancestry; or in other words: zooms up one level.
        /// </summary>
        /// <param name="element">The reference element.</param>
        /// <param name="track"></param>
        /// <param name="animated">Animated zoom?</param>
        public void ZoomOntoParent(FrameworkElement element, bool track, bool animated)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            if (Animation == null)
                return;

            if (!(bool)element.GetValue(ZoomTargetProperty))
            {
                element = ZTreeHelper.GetClosestZoomTarget(this, element);
                if (element == null)
                    return;
            }

            if (element.Parent == null)
                return;

            ZoomOnto(element.Parent as FrameworkElement, track, animated);
        }

        /// <summary>
        /// Pans by a specific vector.
        /// </summary>
        /// <param name="by">Panning vector.</param>
        /// <param name="animated">Animated pan?</param>
        public void PanBy(Vector by, bool animated)
        {
            if (Animation == null)
                return;

            FocusedControl = null;
            var p = Animation.Position;
            p.Pan += by;
            Animation.Goto(ApplyLandscapeConstraints(p), animated);
        }

        #endregion

        #region Viewport

        public Rect Viewport
        {
            get
            {
                var viewport = new Rect
                               {
                                   X = Animation.Position.Pan.X,
                                   Y = Animation.Position.Pan.Y,
                                   Width = ActualWidth / Animation.Position.Zoom,
                                   Height = ActualHeight / Animation.Position.Zoom
                               };
                return viewport;
            }
        }

        public Rect Viewport0
        {
            get
            {
                var viewport = new Rect
                {
                    X = -Animation.Position.Pan.X,
                    Y = -Animation.Position.Pan.Y,
                    Width = ActualWidth / Animation.Position.Zoom,
                    Height = ActualHeight / Animation.Position.Zoom
                };
                return viewport;
            }
        }

        #endregion

        #region Index

        /// <summary>
        /// Creates an index over all children of the visual tree.
        /// </summary>
        public void BuildIndex()
        {
            _indexedElements = new ArrayList();

            var children = VisualTreeHelper.GetChildrenCount(this);
            for (var i = 0; i < children; i++)
                BuildIndex(VisualTreeHelper.GetChild(this, i));

            Console.Beep();
        }

        /// <summary>
        /// Creates an index of elements that need to be updated regularly.
        /// The index is subsequently used instead of manual element tree recursion,
        /// but needs to be maintained manually.
        /// </summary>
        /// <param name="o"></param>
        private void BuildIndex(DependencyObject o)
        {
            if (o is ZComponent)
                // ZComponents are always indexed (semantic zooming)
                _indexedElements.Add(o);
            else
                // Index elements that have fixed-shape zooming modifiers
                if (o.GetValue(ZoomModifierProperty) != null
                    || o.GetValue(ZoomModifierGroupProperty) != null)
                    _indexedElements.Add(o);

            // Invisible objects will not be indexed
            if (o is UIElement && (o as UIElement).Visibility != Visibility.Visible)
                return;

            // Indexing stops at nested information landscapes.
            if (o != this && o is ZInformationLandscape)
                return;

            // Recursively continue indexing
            var children = VisualTreeHelper.GetChildrenCount(o);
            for (var i = 0; i < children; i++)
                BuildIndex(VisualTreeHelper.GetChild(o, i));
        }

        #endregion
    }
}